﻿
Imports std

Public MustInherit Class distrrule_progress

    Private Class machine_selector_status
        Public percentage As percentage_t = Nothing
        Public tried As memblock(Of Boolean) = Nothing
        Public triedTimes As Int64 = 0
        Public index As Int64 = 0
        Public selector As machine_selector = Nothing

        Public Sub New(ByVal s As machine_selector)
            assert(Not s Is Nothing)
            percentage = New percentage_t()
            tried = New memblock(Of Boolean)()
            tried.size() = s.size()
            memset(+tried, 0, tried.size(), False)
            triedTimes = 0
            index = 0
            selector = s
        End Sub
    End Class

    Public Const NOT_START_STEP_INDEX As Int64 = npos
    Private _startticks As Int64 = npos
    Private _finishticks As Int64 = npos
    Private _jobid As String = Nothing
    Private _failed As Boolean = False
    Private command As vector(Of String) = Nothing
    Private rule As distrrule = Nothing
    Private step_index As Int64 = 0
    Private step_status As vector(Of machine_selector_status) = Nothing

    Protected MustOverride Function status(ByVal machine As String, ByVal jobid As String, _
                                           ByRef percentage As percentage_t) As Boolean
    Protected MustOverride Function run(ByVal machine As String, ByVal jobid As String, _
                                        ByVal cmd As String) As Boolean

    Public Function percentage() As percentage_t
        assert(step_index < rule.size() AndAlso step_index >= -1)
        If not_started() Then
            Return percentage_t.NOT_START
        ElseIf finish() Then
            If failed() Then
                Return percentage_t.FAILED
            Else
                Return percentage_t.FINISHED
            End If
        Else
            Dim p As Int64 = 0
            For i As Int64 = 0 To step_status.size() - 1
                p += +(step_status(i).percentage)
            Next
            Return New percentage_t(step_index \ rule.size() + p \ step_status.size() \ rule.size())
        End If
    End Function

    Public Function jobid() As String
        Return _jobid
    End Function

    Public Function startticks() As Int64
        Return _startticks
    End Function

    Public Function finishticks() As Int64
        Return _finishticks
    End Function

    Public Function failed() As Boolean
        Return _failed
    End Function

    Private Sub _new()
        _startticks = npos
        _finishticks = npos
        step_index = NOT_START_STEP_INDEX
        _failed = False
        step_status = New vector(Of machine_selector_status)()
    End Sub

    Private Shared Function finished_selector(ByVal s As machine_selector_status) As Boolean
        assert(Not s Is Nothing)
        Return s.percentage = percentage_t.FINISHED
    End Function

    Private Function finished_step() As Boolean
        If not_started() Then
            If _startticks = npos Then
                _startticks = Now.Ticks()
            End If
            Return True
        End If
        Dim i As Int64 = 0
        For i = 0 To step_status.size() - 1
            If Not finished_selector(step_status(i)) Then
                Exit For
            End If
        Next
        Return i = step_status.size()
    End Function

    Private Function current_step() As [step]
        assert(Not rule Is Nothing AndAlso rule.availableIndex(step_index))
        Return rule(step_index)
    End Function

    Private Function current_command() As String
        assert(Not command Is Nothing)
        Return command.modget(step_index)
    End Function

    Private Sub next_step()
        If not_started() Then
            step_index = 0
        Else
            step_index += 1
        End If
        If Not finish() Then
            step_status.clear()
            For i As Int64 = 0 To current_step().size() - 1
                step_status.push_back(New machine_selector_status(current_step()(i)))
            Next
            start_selector()
        End If
    End Sub

    Private Shared Function machine(ByVal s As machine_selector_status) As String
        assert(Not s Is Nothing)
        Return s.selector(s.index)
    End Function

    Private Sub update_status()
        For i As Int64 = 0 To step_status.size() - 1
            If Not status(machine(step_status(i)), jobid(), step_status(i).percentage) Then
                raiseError("cannot get status of machine " + machine(step_status(i)) + " for job " + jobid() + _
                           ", mark as failed.", errorHandle.errorType.warning)
                step_status(i).percentage = percentage_t.FAILED
            End If
        Next
    End Sub

    Private Shared Function random_select(ByVal s As machine_selector_status) As Boolean
        assert(Not s Is Nothing)
        assert(s.triedTimes <= s.tried.size())
        If s.triedTimes = s.selector.size() Then
            Return False
        Else
            If s.triedTimes > (s.selector.size() >> 1) Then
                For s.index = 0 To s.tried.size() - 1
                    If Not s.tried(s.index) Then
                        Exit For
                    End If
                Next
            Else
                While True
                    s.index = rnd(0, s.selector.size(), True)
                    If Not s.tried(s.index) Then
                        s.tried(s.index) = True
                        Exit While
                    End If
                End While
            End If
            s.triedTimes += 1
            assert(s.index >= 0 AndAlso s.index < s.tried.size())
            Return True
        End If
    End Function

    Private Sub random_start(ByVal s As machine_selector_status)
        If Not random_select(s) Then
            raiseError("do not have other machine can selected for job " + jobid() + " on step " + _
                       Convert.ToString(step_index) + ", mark job failed.", errorHandle.errorType.warning)
            _failed = True
        End If
        If Not run(machine(s), jobid(), command(step_index)) Then
            raiseError("cannot start command " + command(step_index) + " on machine " + machine(s) + _
                       " for job " + jobid() + ", mark as failed.", errorHandle.errorType.warning)
            s.percentage = percentage_t.FAILED
        End If
    End Sub

    Private Sub _start_selector(ByVal percentage_rule As percentage_t)
        For i As Int64 = 0 To step_status.size() - 1
            If step_status(i).percentage = percentage_rule Then
                random_start(step_status(i))
            End If
        Next
    End Sub

    Private Sub start_selector()
        _start_selector(percentage_t.NOT_START)
    End Sub

    Private Sub restart_failed_selector()
        _start_selector(percentage_t.FAILED)
    End Sub

    Public Sub [step]()
        If Not finish() Then
            If finished_step() Then
                next_step()
            ElseIf Not finish() Then
                update_status()
                restart_failed_selector()
            End If
        End If
    End Sub

    Public Sub New(ByVal jobid As String, ByVal cmd As vector(Of String), ByVal rule As distrrule)
        copy(_jobid, jobid)
        copy(command, cmd)
        copy(Me.rule, rule)
        _new()
    End Sub

    Public Function finish() As Boolean
        assert(step_index <= rule.size())
        Dim rtn As Boolean = False
        rtn = failed() OrElse step_index = rule.size()
        If rtn AndAlso _finishticks = npos Then
            _finishticks = Now.Ticks()
        End If
        Return rtn
    End Function

    Public Function not_started() As Boolean
        Return step_index = NOT_START_STEP_INDEX
    End Function

    Private Shared Function column(ByVal o As Object, Optional ByVal addtab As Boolean = True) As String
        Return Convert.ToString(o) + ifoper(addtab, character.tab, emptyString)
    End Function

    Private Shared Function column(ByVal cmd As vector(Of String), Optional ByVal addtab As Boolean = True) As String
        assert(Not cmd Is Nothing)
        Return column(cmd.ToString(character.sheffer), addtab)
    End Function

    Public Overrides Function ToString() As String
        Return column(jobid()) + column(failed()) + column(command) + column(rule, False)
    End Function
End Class
